Java IO

Java IO 和 Java NIO。

Java IO的分类

Java IO分类
字节流读取单个字节,字符流读取单个字符。字节流用来处理二进制文件,字符流用来处理文本文件。简而言之,字节是个计算机看的,字符才是给人看的。

Java NIO

  1. NIO简介

    NIO(Non-blocking I/O,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。

  2. NIO与IO的区别

    • IO是面向流的,NIO是面向缓冲的
    • IO是阻塞的,NIO是不阻塞的
    • NIO有选择器,IO没有
  3. 读写数据方式

    • 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。
    • 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。
  4. 核心组件介绍

    • Channel
    • Buffer
    • Selector

NIO之Channel

  1. Channel介绍

    通道(channel)是一种用于磁盘文件的抽象,它使我们可以访问诸如内存映射、文件加锁机制以及文件间快速数据传递等操作系统特性。

  2. FileChannel的使用

    1
    2
    3
    FileChannel channel = FileChannel.open(path, options);
    MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0,
    length);

    通过FileChannel的map方法可以获得一个ByteBuffer,支持三种映射模式:

    • READ_ONLY: 产生的缓冲区只读
    • READ_WRITE: 产生的缓冲区可写,且对缓冲的修改会写回文件中
    • PRIVATE: 缓冲区可写,对缓冲区的修改不写回文件中

    有了缓冲区就可以通过缓冲区进行读写操作了。

  3. SocketChannel 和 ServerSocketChannel 的使用

    对于 SocketChannel 如果直接在默认模式(阻塞模式)下使用 connect() 方法直接连接,和使用 Socket 对象直接连接一样会造成阻塞;如果在非阻塞模式下使用 connect() 方法连接时采用并发连接,它发起对请求地址的连接并且立即返回值。如果返回值是true,说明连接立即建立了(这可能是本地环回连接);如果连接不能立即建立,connect( )方法会返回false且并发地继续连接建立过程。

    而在服务端的 ServerSocketChannel 对象负责监听服务器上的端口,使用之前需要通过 ServerSocketChannel.socket() 方法获取 ServerSocket 对象并绑定端口。在传统的基于流的Socket网络编程中,服务端为每一个请求创建一个线程用于读写数据,而使用ServerSocketChannel在服务端编程,我们往往配合Selector选择器使用,在服务端我们将特定的Accpet、Read、Write事件注册到选择器上,由选择器帮我检查操作系统内核是否可读和可写,如有对应的事件满足要求,选择器会通知调用者线程。

    相对于传统Socket编程,使用基于缓冲区的NIO编程在如下几点不会阻塞调用者线程:

    1. 客户端connect( )方法不会阻塞

    2. 服务端accept()方法不会阻塞

    3. Socket的读read()方法不会阻塞

NIO之Buffer

  1. 缓冲区介绍
    • Java NIO Buffers用于和NIO Channel交互。 我们从Channel中读取数据到buffers里,从Buffer把数据写入到Channels;
    • Buffer本质上就是一块内存区;
    • 一个Buffer有三个属性是必须掌握的,分别是:capacity容量、position位置、limit界限。
  2. Buffer常见的方法
    • clear():将位置复位到0,并将界限设置到容量,为写出做好准备;
    • flip():将界限设置到位置,并将位置复位到0,为读入做好准备;
    • rewind():将读写位置复位到0,并保持界限不变,使这个缓冲区为重新读入相同的值做好准备;
    • position():返回这个缓冲区的位置
  3. Buffer的使用方式

    • 分配缓冲区:

      1
      ByteBuffer buf = ByteBuffer.allocate(28);
    • 写入数据:

      1
      2
      3
      4
      5
      //从Channel中写入数据
      int bytesRead = inChannel.read(buf);

      //通过put()写入数据
      buf.put(dataArray);

NIO之Selector

  1. 选择器介绍

    Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。

    使用Selector的好处在于: 使用更少的线程来就可以来处理通道了,相比使用多个线程,避免了线程上下文切换带来的开销。

  2. Selector的使用方法

    • Selector的创建

      1
      Selector selector = Selector.open();
    • 注册Channel到Selector

      1
      2
      channel.configureBlocking(false);
      SelectionKey key = channel.register(selector, Selectionkey.OP_READ);

      与 Selector 一起使用时,channel 必须处于非阻塞模式下。

      register()方法的第二个参数是一个“interest集合”,对应可以监听的四种事件:

      1. Connect
      2. Accept
      3. Read
      4. Write

      这四种事件用SelectionKey的四个常量来表示:

      1. SelectionKey.OP_CONNECT
      2. SelectionKey.OP_ACCEPT
      3. SelectionKey.OP_READ
      4. SelectionKey.OP_WRITE

      如果需要监听多个事件可以用 | 组合。

    • SelectionKey介绍

      当向 Selector 注册 Channel 之后返回一个SelectionKey对象,这个对象包含:

      • interest集合:同上
      • ready集合:通道已经准备就绪的操作的集合。
      • Selector
      • Channel
    • 通过Selector选择通道
      调用 select() 方法会返回已经就绪的通道个数,通过 key.channel()可以返回通道。

    • wakeUp()
      某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回。

    • close()
      用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。